# -*- coding: utf-8 -*-
#
# fusion.tracker.Tracker_collection
#
# Copyright 2007 Helsinki Institute for Information Technology
# and the authors.
#
# Authors: Toni Ruottu <toni.ruottu@iki.fi>
#          Harri Hämäläinen <harri.hamalainen@helsinki.fi>

# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

'''
Api for bittorrent tracker collections.
'''

import datetime
import simplejson
import random
from fusion.db.exceptions import *
from fusion.db.db import bytes_to_hex, hex_to_bytes
from fusion.tracker.pybencode import PyBencode
from fusion.core.identity import generate_identity, decode_identity

class Tracker_collection(object):

    def __init__(self, tracker):
        self.collection = tracker
        object.__init__(self)

    def published(self,db):

        entries = db.keys(self.collection, 'island')
        files = []
        for i in entries:
            try:
                if len(i) > 46 and i[40:46]=='/file/' and not ('/' in i[:40]):
                    files.append(i[:40])
            except:
                pass
        return set(files)

    def discover(self,db,info_hash):

        tracker = 'tracker/' + bytes_to_hex(info_hash) + '/peers'
        entries = db.get_view(self.collection, tracker)
        peers = {}

        for entry in entries.values():
            try:
                peers.update(simplejson.loads(entry))
            except:
                pass

        for k in peers.keys():
            if len(k) != 20:
                del peers[k]

        return self.fresh(peers)

    def put_my_peers(self,db,info_hash,mypeers):
        
        info_hash_hex = bytes_to_hex(info_hash)
        tracker = 'tracker/' + info_hash_hex + '/peers'

        bt_identity = self.bittorrent_identity(db)
        db.post(self.collection, tracker, simplejson.dumps(mypeers), bt_identity)

    def get_my_peers(self,db,info_hash):
        info_hash_hex = bytes_to_hex(info_hash)
        tracker = 'tracker/' + info_hash_hex + '/peers'

        fingerprint = bytes_to_hex(self.bittorrent_identity(db).get_fingerprint())
        mypeers_json = db.get(self.collection, tracker + '/0x' + fingerprint)
        mypeers = {}

        if mypeers_json:
            try:
                mypeers = self.fresh(simplejson.loads(mypeers_json))
            except:
                mypeers = {}

        for k in mypeers.keys():
            if len(k) != 20:
                del mypeers[k]

        return mypeers

    def download_complete(self,db,info_hash):

        
        key = 'tracker/' + bytes_to_hex(info_hash) + '/completed'
        time = self.stamp_now()
        bt_identity = self.bittorrent_identity(db)

        db.post(self.collection, key, simplejson.dumps(time), bt_identity)


    def advertise(self,db,info_hash,newpeers):

        mypeers = self.get_my_peers(db,info_hash)
        mypeers.update(newpeers)
        self.put_my_peers(db,info_hash,mypeers)

    def clear_advertisements(self,db,info_hash):

        mypeers = {}
        self.put_my_peers(db,info_hash,mypeers)


    def bittorrent_identity(self,db):
        
        value = db.get_local_info('bittorrent_identity')
        bt_identity = value and decode_identity(value)

        if not bt_identity:
            bt_identity = generate_identity()
            db.set_local_info('bittorrent_identity', bt_identity.encode_private())

        return bt_identity

    def fresh(self,peers,ttl=3600):

        fresh_peers = {}
        now = datetime.datetime(1,1,1).utcnow()
        ttl_s = datetime.timedelta(seconds=ttl)

        for peer_id, peer in peers.items():
            try:
                stamp_then = peer['timestamp']
                then = datetime.datetime(1,1,1).strptime(stamp_then, '%Y-%m-%dT%H:%M:%SZ')
                if( then + ttl_s > now ):
                    fresh_peers[peer_id] = peer
            except:
                pass

        return fresh_peers

    def peer_advertisement(self,peers):
        keys = ['peer_id','ip','port']
        integers = ['port']

        adverts = []

        for peer_id, peer in peers.items():
            advert = {}
            advert['peer_id'] = peer_id

            for key in peer.keys():
                if key in keys:
                    if key in integers:
                        advert[key] = int(peer[key])
                    else:
                        advert[key] = peer[key]

            if 'ips' in peer.keys():
                for ip in peer['ips']:
                    advert['ip'] = ip
                    adverts.append(advert)
            else:
                adverts.append(advert)

        return adverts

    def count_seeds(self,peers):
        seeds = 0
        for peer in peers.values():
            try:
                if int(peer['left']) == 0:
                    seeds += 1
            except:
                pass

        return seeds

    def count_completed(self, db, info_hash):
        return len(db.keys(self.collection, 'tracker/' + bytes_to_hex(info_hash) + '/completed'))

    def tracker_response(self,peers,numwant=50):

        response = {}

        response['interval'] = 3600

        complete = self.count_seeds(peers)
        response['complete'] = complete
        response['incomplete'] = len(peers) - complete

        selected = peers

        if len(peers) > numwant:
            selected = random.sample(peers,numwant)

        response['peers'] = self.peer_advertisement(selected)

        return PyBencode().encode(response)

    def stamp_now(self):
        now = datetime.datetime(1,1,1).utcnow()
        stamp = now.strftime('%Y-%m-%dT%H:%M:%SZ')
        return stamp
